home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-01 / stevi69s.zip / SEARCH.C < prev    next >
Text File  |  1990-04-23  |  19KB  |  982 lines

  1. /* $Header: /nw/tony/src/stevie/src/RCS/search.c,v 1.16 89/08/06 09:50:51 tony Exp $
  2.  *
  3.  * This file contains various searching-related routines. These fall into
  4.  * three groups: string searches (for /, ?, n, and N), character searches
  5.  * within a single line (for f, F, t, T, etc), and "other" kinds of searches
  6.  * like the '%' command, and 'word' searches.
  7.  *
  8.  * v1.1 Toad Hall Tweak, 20 Apr 90
  9.  */
  10.  
  11. #include "stevie.h"
  12. #include "regexp.h"    /* Henry Spencer's (modified) reg. exp. routines */
  13.  
  14. /*
  15.  * String searches
  16.  *
  17.  * The actual searches are done using Henry Spencer's regular expression
  18.  * library.
  19.  */
  20.  
  21. #define    BEGWORD    "([^a-zA-Z0-9_]|^)"    /* replaces "\<" in search strings */
  22. #define    ENDWORD    "([^a-zA-Z0-9_]|$)"    /* likewise replaces "\>" */
  23.  
  24. #define    BEGCHAR(c)    (islower(c) || isupper(c) || isdigit(c) || ((c) == '_'))
  25.  
  26. bool_t    begword;    /* does the search include a 'begin word' match */
  27.  
  28. /*
  29.  * mapstring(s) - map special backslash sequences
  30.  */
  31. static char *
  32. mapstring(s)
  33. register char    *s;
  34. {
  35.     static    char    ns[80];
  36.     register char    *p;
  37.  
  38.     begword = FALSE;
  39.  
  40.     for (p = ns; *s ;s++) {
  41.         if (*s != '\\') {    /* not an escape */
  42.             *p++ = *s;
  43.             continue;
  44.         }
  45.         switch (*++s) {
  46.         case '/':
  47.             *p++ = '/';
  48.             break;
  49.  
  50.         case '<':
  51.             strcpy(p, BEGWORD);
  52.             p += strlen(BEGWORD);
  53.             begword = TRUE;
  54.             break;
  55.  
  56.         case '>':
  57.             strcpy(p, ENDWORD);
  58.             p += strlen(ENDWORD);
  59.             break;
  60.  
  61.         default:
  62.             *p++ = '\\';
  63.             *p++ = *s;
  64.             break;
  65.         }
  66.     }
  67.     *p++ = NUL;
  68.  
  69.     return ns;
  70. }
  71.  
  72. static char *laststr = NULL;
  73. static int lastsdir;
  74.  
  75. static LPTR *
  76. ssearch(dir,str)
  77. int    dir;    /* FORWARD or BACKWARD */
  78. char    *str;
  79. {
  80.     LPTR    *bcksearch(), *fwdsearch();
  81.     LPTR    *pos;
  82.     char    *old_ls = laststr;
  83.  
  84.     reg_ic = P(P_IC);    /* tell the regexp routines how to search */
  85.  
  86.     laststr = strsave(str);
  87.     lastsdir = dir;
  88.  
  89.     if (old_ls != NULL)
  90.         free(old_ls);
  91.  
  92.     if (dir == BACKWARD) {
  93.         smsg("?%s", laststr);
  94.         pos = bcksearch(mapstring(laststr));
  95.     } else {
  96.         smsg("/%s", laststr);
  97.         pos = fwdsearch(mapstring(laststr));
  98.     }
  99.  
  100.     /*
  101.      * This is kind of a kludge, but its needed to make
  102.      * 'beginning of word' searches land on the right place.
  103.      */
  104.     if (pos != NULL && begword) {
  105.         if (pos->index != 0 || !BEGCHAR(pos->linep->s[0]))
  106.             pos->index += 1;
  107.     }
  108.     return pos;
  109. }
  110.  
  111. bool_t
  112. dosearch(dir,str)
  113. int    dir;
  114. char    *str;
  115. {
  116.     LPTR    *p;
  117.  
  118.     if (str == NULL)
  119.         str = laststr;
  120.  
  121.     got_int = FALSE;
  122.  
  123.     if ((p = ssearch(dir,str)) == NULL) {
  124.         if (got_int)
  125.             msg("Interrupt");
  126.         else
  127.             msg("Pattern not found");
  128.  
  129.         got_int = FALSE;
  130.         return FALSE;
  131.     } else {
  132. /*        LPTR savep; v1.1 never used */
  133.  
  134.         cursupdate();
  135.         /*
  136.          * if we're backing up, we make sure the line we're on
  137.          * is on the screen.
  138.          */
  139.         setpcmark();
  140. /* v1.1    *Curschar = savep = *p; */
  141.         *Curschar = *p;            /* v1.1 */
  142.         set_want_col = TRUE;
  143.         cursupdate();
  144.  
  145.         return TRUE;
  146.     }
  147. }
  148.  
  149. #define    OTHERDIR(x)    (((x) == FORWARD) ? BACKWARD : FORWARD)
  150.  
  151. bool_t
  152. repsearch(flag)
  153. int    flag;
  154. {
  155.     int    dir = lastsdir;
  156.     bool_t    found;
  157.  
  158.     if ( laststr == NULL ) {
  159.         beep();
  160.         return FALSE;
  161.     }
  162.  
  163.     found = dosearch(flag ? OTHERDIR(lastsdir) : lastsdir, laststr);
  164.  
  165.     /*
  166.      * We have to save and restore 'lastsdir' because it gets munged
  167.      * by ssearch() and winds up saving the wrong direction from here
  168.      * if 'flag' is true.
  169.      */
  170.     lastsdir = dir;
  171.  
  172.     return found;
  173. }
  174.  
  175. /*
  176.  * regerror - called by regexp routines when errors are detected.
  177.  */
  178. void
  179. regerror(s)
  180. char    *s;
  181. {
  182.     emsg(s);
  183. }
  184.  
  185. static LPTR *
  186. fwdsearch(str)
  187. register char    *str;
  188. {
  189.     static LPTR    infile;
  190.     register LPTR    *p;
  191.     regexp    *prog;
  192.  
  193.     register char    *s;
  194.     register int    i;
  195.  
  196.     if ((prog = regcomp(str)) == NULL) {
  197.         emsg("Invalid search string");
  198.         return NULL;
  199.     }
  200.  
  201.     p = Curschar;
  202.     i = Curschar->index + 1;
  203.     do {
  204.         s = p->linep->s + i;
  205.  
  206.         if (regexec(prog, s, i == 0)) {        /* got a match */
  207.             infile.linep = p->linep;
  208.             infile.index = (int) (prog->startp[0] - p->linep->s);
  209.             free((char *)prog);
  210.             return (&infile);
  211.         }
  212.         i = 0;
  213.  
  214.         if (got_int)
  215.             goto fwdfail;
  216.  
  217.     } while ((p = nextline(p)) != NULL);
  218.  
  219.     /*
  220.      * If wrapscan isn't set, then don't scan from the beginning
  221.      * of the file. Just return failure here.
  222.      */
  223.     if (!P(P_WS))
  224.         goto fwdfail;
  225.  
  226.     /* search from the beginning of the file to Curschar */
  227.     for (p = Filemem; p != NULL ;p = nextline(p)) {
  228.         s = p->linep->s;
  229.  
  230.         if (regexec(prog, s, TRUE)) {        /* got a match */
  231.             infile.linep = p->linep;
  232.             infile.index = (int) (prog->startp[0] - s);
  233.             free((char *)prog);
  234.             return (&infile);
  235.         }
  236.  
  237.         if (p->linep == Curschar->linep)
  238.             break;
  239.  
  240.         if (got_int)
  241.             goto fwdfail;
  242.     }
  243.  
  244. fwdfail:
  245.     free((char *)prog);
  246.     return NULL;
  247. }
  248.  
  249. static LPTR *
  250. bcksearch(str)
  251. char    *str;
  252. {
  253.     static LPTR    infile;
  254.     register LPTR    *p = &infile;
  255.     register char    *s;
  256.     register int    i;
  257.     register char    *match;
  258.     regexp    *prog;
  259.  
  260.     /* make sure str isn't empty */
  261.     if (str == NULL || *str == NUL)
  262.         return NULL;
  263.  
  264.     if ((prog = regcomp(str)) == NULL) {
  265.         emsg("Invalid search string");
  266.         return NULL;
  267.     }
  268.  
  269.     *p = *Curschar;
  270.     if (dec(p) == -1) {    /* already at start of file? */
  271.         *p = *Fileend;
  272.         p->index = strlen(p->linep->s) - 1;
  273.     }
  274.  
  275.     if (begword)        /* so we don't get stuck on one match */
  276.         dec(p);
  277.  
  278.     i = p->index;
  279.  
  280.     do {
  281.         s = p->linep->s;
  282.  
  283.         if (regexec(prog, s, TRUE)) {    /* match somewhere on line */
  284.  
  285.             /*
  286.              * Now, if there are multiple matches on this line,
  287.              * we have to get the last one. Or the last one
  288.              * before the cursor, if we're on that line.
  289.              */
  290.             match = prog->startp[0];
  291.  
  292.             while (regexec(prog, prog->endp[0], FALSE)) {
  293.                 if ((i >= 0) && ((prog->startp[0] - s) > i))
  294.                     break;
  295.                 match = prog->startp[0];
  296.             }
  297.  
  298.             if ((i >= 0) && ((match - s) > i)) {
  299.                 i = -1;
  300.                 continue;
  301.             }
  302.  
  303.             infile.linep = p->linep;
  304.             infile.index = (int) (match - s);
  305.             free((char *)prog);
  306.             return (&infile);
  307.         }
  308.         i = -1;
  309.  
  310.         if (got_int)
  311.             goto bckfail;
  312.  
  313.     } while ((p = prevline(p)) != NULL);
  314.  
  315.     /*
  316.      * If wrapscan isn't set, bag the search now
  317.      */
  318.     if (!P(P_WS))
  319.         goto bckfail;
  320.  
  321.     /* search backward from the end of the file */
  322.     p = prevline(Fileend);
  323.     do {
  324.         s = p->linep->s;
  325.  
  326.         if (regexec(prog, s, TRUE)) {    /* match somewhere on line */
  327.  
  328.             /*
  329.              * Now, if there are multiple matches on this line,
  330.              * we have to get the last one.
  331.              */
  332.             match = prog->startp[0];
  333.  
  334.             while (regexec(prog, prog->endp[0], FALSE))
  335.                 match = prog->startp[0];
  336.  
  337.             infile.linep = p->linep;
  338.             infile.index = (int) (match - s);
  339.             free((char *)prog);
  340.             return (&infile);
  341.         }
  342.  
  343.         if (p->linep == Curschar->linep)
  344.             break;
  345.  
  346.         if (got_int)
  347.             goto bckfail;
  348.  
  349.     } while ((p = prevline(p)) != NULL);
  350.  
  351. bckfail:
  352.     free((char *)prog);
  353.     return NULL;
  354. }
  355.  
  356. /*
  357.  * dosub(lp, up, cmd)
  358.  *
  359.  * Perform a substitution from line 'lp' to line 'up' using the
  360.  * command pointed to by 'cmd' which should be of the form:
  361.  *
  362.  * /pattern/substitution/g
  363.  *
  364.  * The trailing 'g' is optional and, if present, indicates that multiple
  365.  * substitutions should be performed on each line, if applicable.
  366.  * The usual escapes are supported as described in the regexp docs.
  367.  */
  368. void
  369. dosub(lp, up, cmd)
  370. LPTR    *lp, *up;
  371. char    *cmd;
  372. {
  373.     LINE    *cp;
  374.     char    *pat, *sub;
  375.     regexp    *prog;
  376.     int    nsubs;
  377.     bool_t    do_all;        /* do multiple substitutions per line */
  378.  
  379.     /*
  380.      * If no range was given, do the current line. If only one line
  381.      * was given, just do that one.
  382.      */
  383.     if (lp->linep == NULL)
  384.         *up = *lp = *Curschar;
  385.     else {
  386.         if (up->linep == NULL)
  387.             *up = *lp;
  388.     }
  389.  
  390.     pat = ++cmd;        /* skip the initial '/' */
  391.  
  392.     while (*cmd) {
  393.         if (*cmd == '\\')    /* next char is quoted */
  394.             cmd += 2;
  395.         else if (*cmd == '/') {    /* delimiter */
  396.             *cmd++ = NUL;
  397.             break;
  398.         } else
  399.             cmd++;        /* regular character */
  400.     }
  401.  
  402.     if (*pat == NUL) {
  403.         emsg("NULL pattern specified");
  404.         return;
  405.     }
  406.  
  407.     sub = cmd;
  408.  
  409.     do_all = FALSE;
  410.  
  411.     while (*cmd) {
  412.         if (*cmd == '\\')    /* next char is quoted */
  413.             cmd += 2;
  414.         else if (*cmd == '/') {    /* delimiter */
  415.             do_all = (cmd[1] == 'g');
  416.             *cmd++ = NUL;
  417.             break;
  418.         } else
  419.             cmd++;        /* regular character */
  420.     }
  421.  
  422.     reg_ic = P(P_IC);    /* set "ignore case" flag appropriately */
  423.  
  424.     if ((prog = regcomp(pat)) == NULL) {
  425.         emsg("Invalid search string");
  426.         return;
  427.     }
  428.  
  429.     nsubs = 0;
  430.  
  431.     for (cp = lp->linep; cp != NULL ;cp = cp->next) {
  432.         if (regexec(prog, cp->s, TRUE)) { /* a match on this line */
  433.             char    *ns, *sns, *p;
  434.  
  435.             /*
  436.              * Get some space for a temporary buffer
  437.              * to do the substitution into.
  438.              */
  439.             sns = ns = alloc(2048);
  440.             if (!sns)  return;
  441.             *sns = NUL;
  442.  
  443.             p = cp->s;
  444.  
  445.             do {
  446.                 for (ns = sns; *ns ;ns++)
  447.                     ;
  448.                 /*
  449.                  * copy up to the part that matched
  450.                  */
  451.                 while (p < prog->startp[0])
  452.                     *ns++ = *p++;
  453.  
  454.                 regsub(prog, sub, ns);
  455.  
  456.                 /*
  457.                  * continue searching after the match
  458.                  */
  459.                 p = prog->endp[0];
  460.  
  461.             } while (regexec(prog, p, FALSE) && do_all);
  462.  
  463.             for (ns = sns; *ns ;ns++)
  464.                 ;
  465.  
  466.             /*
  467.              * copy the rest of the line, that didn't match
  468.              */
  469.             while (*p)
  470.                 *ns++ = *p++;
  471.  
  472.             *ns = NUL;
  473.  
  474.             free(cp->s);        /* free the original line */
  475.             cp->s = strsave(sns);    /* and save the modified str */
  476.             cp->size = strlen(cp->s) + 1;
  477.             free(sns);        /* free the temp buffer */
  478.             nsubs++;
  479.             CHANGED;
  480.         }
  481.         if (cp == up->linep)
  482.             break;
  483.     }
  484.  
  485.     if (nsubs) {
  486.         updatescreen();
  487.         if (nsubs >= P(P_RP))
  488.             smsg("%d substitution%c", nsubs, (nsubs>1) ? 's' : ' ');
  489.     } else
  490.         msg("No match");
  491.  
  492.     free((char *)prog);
  493. }
  494.  
  495. /*
  496.  * doglob(cmd)
  497.  *
  498.  * Execute a global command of the form:
  499.  *
  500.  * g/pattern/X
  501.  *
  502.  * where 'x' is a command character, currently one of the following:
  503.  *
  504.  * d    Delete all matching lines
  505.  * p    Print all matching lines
  506.  *
  507.  * The command character (as well as the trailing slash) is optional, and
  508.  * is assumed to be 'p' if missing.
  509.  */
  510. void
  511. doglob(lp, up, cmd)
  512. LPTR    *lp, *up;
  513. char    *cmd;
  514. {
  515.     LINE    *cp;
  516.     char    *pat;
  517.     regexp    *prog;
  518.     int    ndone;
  519.     char    cmdchar = NUL;    /* what to do with matching lines */
  520.  
  521.     /*
  522.      * If no range was given, do every line. If only one line
  523.      * was given, just do that one.
  524.      */
  525.     if (lp->linep == NULL) {
  526.         *lp = *Filemem;
  527.         *up = *Fileend;
  528.     } else {
  529.         if (up->linep == NULL)
  530.             *up = *lp;
  531.     }
  532.  
  533.     pat = ++cmd;        /* skip the initial '/' */
  534.  
  535.     while (*cmd) {
  536.         if (*cmd == '\\')    /* next char is quoted */
  537.             cmd += 2;
  538.         else if (*cmd == '/') {    /* delimiter */
  539.             cmdchar = cmd[1];
  540.             *cmd++ = NUL;
  541.             break;
  542.         } else
  543.             cmd++;        /* regular character */
  544.     }
  545.     if (cmdchar == NUL)
  546.         cmdchar = 'p';
  547.  
  548.     reg_ic = P(P_IC);    /* set "ignore case" flag appropriately */
  549.  
  550.     if (cmdchar != 'd' && cmdchar != 'p') {
  551.         emsg("Invalid command character");
  552.         return;
  553.     }
  554.  
  555.     if ((prog = regcomp(pat)) == NULL) {
  556.         emsg("Invalid search string");
  557.         return;
  558.     }
  559.  
  560.     msg("");
  561.     ndone = 0;
  562.     got_int = FALSE;
  563.  
  564.     for (cp = lp->linep; cp != NULL && !got_int ;cp = cp->next) {
  565.         if (regexec(prog, cp->s, TRUE)) { /* a match on this line */
  566.             switch (cmdchar) {
  567.  
  568.             case 'd':        /* delete the line */
  569.                 if (Curschar->linep != cp) {
  570.                     LPTR    savep;
  571.  
  572.                     savep = *Curschar;
  573.                     Curschar->linep = cp;
  574.                     Curschar->index = 0;
  575.                     delline(1, FALSE);
  576.                     *Curschar = savep;
  577.                 } else
  578.                     delline(1, FALSE);
  579.                 break;
  580.  
  581.             case 'p':        /* print the line */
  582.                 prt_line(cp->s);
  583.                 outstr("\r\n");
  584.                 break;
  585.             }
  586.             ndone++;
  587.         }
  588.         if (cp == up->linep)
  589.             break;
  590.     }
  591.  
  592.     if (ndone) {
  593.         switch (cmdchar) {
  594.  
  595.         case 'd':
  596.             updatescreen();
  597.             if (ndone >= P(P_RP) || got_int)
  598.                 smsg("%s%d fewer line%c",
  599.                     got_int ? "Interrupt: " : "",
  600.                     ndone,
  601.                     (ndone > 1) ? 's' : ' ');
  602.             break;
  603.  
  604.         case 'p':
  605.             wait_return();
  606.             break;
  607.         }
  608.     } else {
  609.         if (got_int)
  610.             msg("Interrupt");
  611.         else
  612.             msg("No match");
  613.     }
  614.  
  615.     got_int = FALSE;
  616.     free((char *)prog);
  617. }
  618.  
  619. /*
  620.  * Character Searches
  621.  */
  622.  
  623. static char lastc = NUL;    /* last character searched for */
  624. static int  lastcdir;        /* last direction of character search */
  625. static int  lastctype;        /* last type of search ("find" or "to") */
  626.  
  627. /*
  628.  * searchc(c, dir, type)
  629.  *
  630.  * Search for character 'c', in direction 'dir'. If type is 0, move to
  631.  * the position of the character, otherwise move to just before the char.
  632.  */
  633. bool_t
  634. searchc(c, dir, type)
  635. char    c;
  636. int    dir;
  637. int    type;
  638. {
  639.     LPTR    save;
  640.  
  641.     save = *Curschar;    /* save position in case we fail */
  642.     lastc = c;
  643.     lastcdir = dir;
  644.     lastctype = type;
  645.  
  646.     /*
  647.      * On 'to' searches, skip one to start with so we can repeat
  648.      * searches in the same direction and have it work right.
  649.      */
  650.     if (type)
  651.         (dir == FORWARD) ? oneright() : oneleft();
  652.  
  653.     while ( (dir == FORWARD) ? oneright() : oneleft() ) {
  654.         if (gchar(Curschar) == c) {
  655.             if (type)
  656.                 (dir == FORWARD) ? oneleft() : oneright();
  657.             return TRUE;
  658.         }
  659.     }
  660.     *Curschar = save;
  661.     return FALSE;
  662. }
  663.  
  664. bool_t
  665. crepsearch(flag)
  666. int    flag;
  667. {
  668.     int    dir = lastcdir;
  669.     int    rval;
  670.  
  671.     if (lastc == NUL)
  672.         return FALSE;
  673.  
  674.     rval = searchc(lastc, flag ? OTHERDIR(lastcdir) : lastcdir, lastctype);
  675.  
  676.     lastcdir = dir;        /* restore dir., since it may have changed */
  677.  
  678.     return rval;
  679. }
  680.  
  681. /*
  682.  * "Other" Searches
  683.  */
  684.  
  685. /*
  686.  * showmatch - move the cursor to the matching paren or brace
  687.  */
  688. LPTR *
  689. showmatch()
  690. {
  691.     static    LPTR    pos;
  692.     int    (*move)(), inc(), dec();
  693.     char    initc = gchar(Curschar);    /* initial char */
  694.     char    findc;                /* terminating char */
  695.     char    c;
  696.     int    count = 0;
  697.  
  698.     pos = *Curschar;        /* set starting point */
  699.  
  700.     switch (initc) {
  701.  
  702.     case '(':
  703.         findc = ')';
  704.         move = inc;
  705.         break;
  706.     case ')':
  707.         findc = '(';
  708.         move = dec;
  709.         break;
  710.     case '{':
  711.         findc = '}';
  712.         move = inc;
  713.         break;
  714.     case '}':
  715.         findc = '{';
  716.         move = dec;
  717.         break;
  718.     case '[':
  719.         findc = ']';
  720.         move = inc;
  721.         break;
  722.     case ']':
  723.         findc = '[';
  724.         move = dec;
  725.         break;
  726.     default:
  727.         return (LPTR *) NULL;
  728.     }
  729.  
  730.     while ((*move)(&pos) != -1) {        /* until end of file */
  731.         c = gchar(&pos);
  732.         if (c == initc)
  733.             count++;
  734.         else if (c == findc) {
  735.             if (count == 0)
  736.                 return &pos;
  737.             count--;
  738.         }
  739.     }
  740.     return (LPTR *) NULL;            /* never found it */
  741. }
  742.  
  743.  
  744. /*
  745.  * The following routines do the word searches performed by the
  746.  * 'w', 'W', 'b', 'B', 'e', and 'E' commands.
  747.  */
  748.  
  749. /*
  750.  * To perform these searches, characters are placed into one of three
  751.  * classes, and transitions between classes determine word boundaries.
  752.  *
  753.  * The classes are:
  754.  *
  755.  * 0 - white space
  756.  * 1 - letters, digits, and underscore
  757.  * 2 - everything else
  758.  */
  759.  
  760. static    int    stype;        /* type of the word motion being performed */
  761.  
  762. #define    C0(c)    (((c) == ' ') || ((c) == '\t') || ((c) == NUL))
  763. #define    C1(c)    (isalpha(c) || isdigit(c) || ((c) == '_'))
  764.  
  765. /*
  766.  * cls(c) - returns the class of character 'c'
  767.  *
  768.  * The 'type' of the current search modifies the classes of characters
  769.  * if a 'W', 'B', or 'E' motion is being done. In this case, chars. from
  770.  * class 2 are reported as class 1 since only white space boundaries are
  771.  * of interest.
  772.  */
  773. static    int
  774. cls(c)
  775. char    c;
  776. {
  777.     if (C0(c))
  778.         return 0;
  779.  
  780.     if (C1(c))
  781.         return 1;
  782.  
  783.     /*
  784.      * If stype is non-zero, report these as class 1.
  785.      */
  786.     return (stype == 0) ? 2 : 1;
  787. }
  788.  
  789.  
  790. /*
  791.  * fwd_word(pos, type) - move forward one word
  792.  *
  793.  * Returns the resulting position, or NULL if EOF was reached.
  794.  */
  795. LPTR *
  796. fwd_word(p, type)
  797. LPTR    *p;
  798. int    type;
  799. {
  800.     static    LPTR    pos;
  801.     int    sclass = cls(gchar(p));        /* starting class */
  802.  
  803.     pos = *p;
  804.  
  805.     stype = type;
  806.  
  807.     /*
  808.      * We always move at least one character.
  809.      */
  810.     if (inc(&pos) == -1)
  811.         return NULL;
  812.  
  813.     if (sclass != 0) {
  814.         while (cls(gchar(&pos)) == sclass) {
  815.             if (inc(&pos) == -1)
  816.                 return NULL;
  817.         }
  818.         /*
  819.          * If we went from 1 -> 2 or 2 -> 1, return here.
  820.          */
  821.         if (cls(gchar(&pos)) != 0)
  822.             return &pos;
  823.     }
  824.  
  825.     /* We're in white space; go to next non-white */
  826.  
  827.     while (cls(gchar(&pos)) == 0) {
  828.         /*
  829.          * We'll stop if we land on a blank line
  830.          */
  831.         if (pos.index == 0 && pos.linep->s[0] == NUL)
  832.             break;
  833.  
  834.         if (inc(&pos) == -1)
  835.             return NULL;
  836.     }
  837.  
  838.     return &pos;
  839. }
  840.  
  841. /*
  842.  * bck_word(pos, type) - move backward one word
  843.  *
  844.  * Returns the resulting position, or NULL if EOF was reached.
  845.  */
  846. LPTR *
  847. bck_word(p, type)
  848. LPTR    *p;
  849. int    type;
  850. {
  851.     static    LPTR    pos;
  852.     int    sclass = cls(gchar(p));        /* starting class */
  853.  
  854.     pos = *p;
  855.  
  856.     stype = type;
  857.  
  858.     if (dec(&pos) == -1)
  859.         return NULL;
  860.  
  861.     /*
  862.      * If we're in the middle of a word, we just have to
  863.      * back up to the start of it.
  864.      */
  865.     if (cls(gchar(&pos)) == sclass && sclass != 0) {
  866.         /*
  867.          * Move backward to start of the current word
  868.          */
  869.         while (cls(gchar(&pos)) == sclass) {
  870.             if (dec(&pos) == -1)
  871.                 return NULL;
  872.         }
  873.         inc(&pos);            /* overshot - forward one */
  874.         return &pos;
  875.     }
  876.  
  877.     /*
  878.      * We were at the start of a word. Go back to the start
  879.      * of the prior word.
  880.      */
  881.  
  882.     while (cls(gchar(&pos)) == 0) {        /* skip any white space */
  883.         /*
  884.          * We'll stop if we land on a blank line
  885.          */
  886.         if (pos.index == 0 && pos.linep->s[0] == NUL)
  887.             return &pos;
  888.  
  889.         if (dec(&pos) == -1)
  890.             return NULL;
  891.     }
  892.  
  893.     sclass = cls(gchar(&pos));
  894.  
  895.     /*
  896.      * Move backward to start of this word.
  897.      */
  898.     while (cls(gchar(&pos)) == sclass) {
  899.         if (dec(&pos) == -1)
  900.             return NULL;
  901.     }
  902.     inc(&pos);            /* overshot - forward one */
  903.  
  904.     return &pos;
  905. }
  906.  
  907. /*
  908.  * end_word(pos, type, in_change) - move to the end of the word
  909.  *
  910.  * There is an apparent bug in the 'e' motion of the real vi. At least
  911.  * on the System V Release 3 version for the 80386. Unlike 'b' and 'w',
  912.  * the 'e' motion crosses blank lines. When the real vi crosses a blank
  913.  * line in an 'e' motion, the cursor is placed on the FIRST character
  914.  * of the next non-blank line. The 'E' command, however, works correctly.
  915.  * Since this appears to be a bug, I have not duplicated it here.
  916.  *
  917.  * There's a strange special case here that the 'in_change' parameter
  918.  * helps us deal with. Vi effectively turns 'cw' into 'ce'. If we're on
  919.  * a word with only one character, we need to stick at the current
  920.  * position so we don't change two words.
  921.  *
  922.  * Returns the resulting position, or NULL if EOF was reached.
  923.  */
  924. LPTR *
  925. end_word(p, type, in_change)
  926. LPTR    *p;
  927. int    type;
  928. bool_t    in_change;
  929. {
  930.     static    LPTR    pos;
  931.     int    sclass = cls(gchar(p));        /* starting class */
  932.  
  933.     pos = *p;
  934.  
  935.     stype = type;
  936.  
  937.     if (inc(&pos) == -1)
  938.         return NULL;
  939.  
  940.     /*
  941.      * If we're in the middle of a word, we just have to
  942.      * move to the end of it.
  943.      */
  944.     if (cls(gchar(&pos)) == sclass && sclass != 0) {
  945.         /*
  946.          * Move forward to end of the current word
  947.          */
  948.         while (cls(gchar(&pos)) == sclass) {
  949.             if (inc(&pos) == -1)
  950.                 return NULL;
  951.         }
  952.         dec(&pos);            /* overshot - forward one */
  953.         return &pos;
  954.     }
  955.  
  956.     /*
  957.      * We were at the end of a word. Go to the end of the next
  958.      * word, unless we're doing a change. In that case we stick
  959.      * at the end of the current word.
  960.      */
  961.     if (in_change)
  962.         return p;
  963.  
  964.     while (cls(gchar(&pos)) == 0) {        /* skip any white space */
  965.         if (inc(&pos) == -1)
  966.             return NULL;
  967.     }
  968.  
  969.     sclass = cls(gchar(&pos));
  970.  
  971.     /*
  972.      * Move forward to end of this word.
  973.      */
  974.     while (cls(gchar(&pos)) == sclass) {
  975.         if (inc(&pos) == -1)
  976.             return NULL;
  977.     }
  978.     dec(&pos);            /* overshot - forward one */
  979.  
  980.     return &pos;
  981. }
  982.